﻿using System;
using System.Net;
using System.Linq;
using System.ServiceModel;
using System.Collections.Concurrent;
using Lypshare.Net.Server.Service;
using System.Threading.Tasks;

namespace Lypshare.Net.Client
{
	/// <summary>
	/// Network session.
	/// </summary>
	/// <exception cref='ArgumentNullException'>
	/// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
	/// </exception>
	/// <exception cref='ArgumentException'>
	/// Is thrown when an argument passed to a method is invalid.
	/// </exception>
	/// <exception cref='InvalidOperationException'>
	/// Is thrown when an operation cannot be performed.
	/// </exception>
    [CallbackBehavior(UseSynchronizationContext = false)]
    public class NetworkSession : INetworkSession, IAccessServiceCallback
    {
        // --------------------------------------------------------
        // Class Events
        // --------------------------------------------------------


        /// <summary>
        /// Occurs when client joined.
        /// </summary>
        public event Action<INetworkSession, ConnectionEventArgs> ClientJoined = null;


        /// <summary>
        /// Occurs when client left.
        /// </summary>
        public event Action<INetworkSession, ConnectionEventArgs> ClientLeft = null;


        /// <summary>
        /// Occurs when data received.
        /// </summary>
        public event Action<INetworkSession, ConnectionDataEventArgs> DataReceived = null;


        /// <summary>
        /// Occurs when data sent.
        /// </summary>
        public event Action<INetworkSession, ConnectionDataEventArgs> DataSent = null;
		
		
		/// <summary>
		/// Occurs when closed.
		/// </summary>
		public event Action<INetworkSession> Closed = null;


        // --------------------------------------------------------
        // Class Variables
        // --------------------------------------------------------


        /// <summary>
        /// The _connections.
        /// </summary>
        private readonly ConcurrentDictionary<int, IConnection> _connections
            = new ConcurrentDictionary<int, IConnection>();


        /// <summary>
        /// The _access point.
        /// </summary>
        private APClient _accessPoint = null;


        // --------------------------------------------------------
        // Class Properties
        // --------------------------------------------------------
		
		
		/// <summary>
		/// Gets the identifier.
		/// </summary>
		/// <value>
		/// The identifier.
		/// </value>
		public int Id
		{
			get { return _accessPoint.Id; }
			
		} // Property: Id
		
		
		/// <summary>
		/// Gets the address.
		/// </summary>
		/// <value>
		/// The address.
		/// </value>
		public IPAddress Address
		{
			get { return _accessPoint.Address; }
			
		} // Property: Address
		
		
		/// <summary>
        /// Gets the connections.
        /// </summary>
        /// <value>
        /// The connections.
        /// </value>
        public IConnection[] Connections
        {
            get { return _connections
                .Values.ToArray(); }

        } // Property: Connections
		
		
		/// <summary>
        /// Gets a value indicating whether this instance is authed.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is authed; otherwise, <c>false</c>.
        /// </value>
        public bool IsAuthed
        {
            get { return _accessPoint.IsAuthed; }

        } // Property: IsAuthed
		
		
		/// <summary>
        /// Gets a value indicating whether this instance is active.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is active; otherwise, <c>false</c>.
        /// </value>
        public bool IsActive
        {
            get { return _accessPoint.IsActive; }

        } // Property: IsActive
		
		
		/// <summary>
		/// Sets a value indicating whether this instance has scope.
		/// </summary>
		/// <value>
		/// <c>true</c> if this instance has scope; otherwise, <c>false</c>.
		/// </value>
		public bool HasScope
		{
			get { return _accessPoint.HasScope; }
			
		} // Property: HasScope
		

        /// <summary>
        /// Gets the scope.
        /// </summary>
        /// <value>
        /// The scope.
        /// </value>
        public String Scope
        {
            get { return _accessPoint.Scope; }

        } // Property: Scope


        // --------------------------------------------------------
        // Class Functions
        // --------------------------------------------------------


        /// <summary>
        /// Initializes a new instance of the <see cref="Lypshare.Net.Client.NetworkSession"/> class.
        /// </summary>
        /// <param name='address'>
        /// Address.
        /// </param>
        /// <param name='port'>
        /// Port.
        /// </param>
        /// <param name='name'>
        /// Name.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        public NetworkSession(String address, int port, String name)
        {
            if (address == null) throw new ArgumentNullException("address");
            if (name == null) throw new ArgumentNullException("name");

            // Initialize access point
            _accessPoint = new APClient(
                address, port, name, this);

        } // Constructor


        /// <summary>
        /// Gets the connection.
        /// </summary>
        /// <returns>
        /// The connection.
        /// </returns>
        /// <param name='clientId'>
        /// Client identifier.
        /// </param>
        /// <exception cref='ArgumentException'>
        /// Is thrown when an argument passed to a method is invalid.
        /// </exception>
        public IConnection GetConnection(int clientId)
        {
            if (!_connections.ContainsKey(clientId))
                throw new ArgumentException("There is no " +
                    "connection with client id " + clientId + ".");

            return _connections[clientId];

        } // GetConnection


        /// <summary>
        /// Auth the specified username and password.
        /// </summary>
        /// <param name='username'>
        /// If set to <c>true</c> username.
        /// </param>
        /// <param name='password'>
        /// If set to <c>true</c> password.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        public bool Auth(String username, String password)
        {
            if (username == null) throw new ArgumentNullException("username");
            if (password == null) throw new ArgumentNullException("password");

            return _accessPoint.Auth(
                username, password);

        } // Connect


        /// <summary>
        /// Join the specified scope.
        /// </summary>
        /// <param name='scope'>
        /// Scope.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        /// <exception cref='InvalidOperationException'>
        /// Is thrown when an operation cannot be performed.
        /// </exception>
        public void Join(String scope)
        {
            if (scope == null)
                throw new ArgumentNullException("scope");

            if (!IsAuthed) throw new InvalidOperationException(
                 "The network session is not authed.");

            if (!IsActive) throw new InvalidOperationException(
                 "The network session is not active.");

            _accessPoint.Join(scope);

        } // Join
		
		
		/// <summary>
		/// Synchronize with the specified connection and port.
		/// </summary>
		/// <param name='connection'>
		/// Connection.
		/// </param>
		/// <param name='port'>
		/// Port.
		/// </param>
		public int Synchronize(IConnection connection, int port)
		{
			if(connection == null)
				throw new ArgumentNullException("connection");
			
			return _accessPoint.Syncronize(
				connection, port);
			
		} // Synchronize


        /// <summary>
        /// Begins the join callback.
        /// </summary>
        /// <param name='clientId'>
        /// Client identifier.
        /// </param>
        /// <param name='address'>
        /// Address.
        /// </param>
        /// <exception cref='NotImplementedException'>
        /// Is thrown when a requested operation is not implemented for a given type.
        /// </exception>
        public void BeginJoinCallback(int clientId, IPAddress address)
        {
            if(address == null)
				throw new ArgumentNullException("address");
			
			var connection = new Connection(
				this, clientId, address);
			
			connection.DataReceived += 
				OnConnectionDataReceived;
			
			connection.DataSent += 
				OnConnectionDataSent;

			_connections.TryAdd(
				clientId, connection);
			
        } // BeginJoinCallback


        /// <summary>
        /// Ends the join callback.
        /// </summary>
        /// <param name='clientId'>
        /// Client identifier.
        /// </param>
        public void EndJoinCallback(int clientId)
        {
            OnClientJoined(new ConnectionEventArgs(
                GetConnection(clientId)));

        } // EndJoinCallback


        /// <summary>
        /// Begins the disconnect callback.
        /// </summary>
        /// <param name='clientId'>
        /// Client identifier.
        /// </param>
        public void DisconnectCallback(int clientId)
        {
            OnClientLeft(new ConnectionEventArgs(
                GetConnection(clientId)));
			
			IConnection connection = null;	
            _connections.TryRemove(clientId, 
               out connection);
				
        } // BeginDisconnectCallback


        /// <summary>
        /// Syncronizes the callback.
        /// </summary>
        /// <param name='clientId'>
        /// Client identifier.
        /// </param>
        /// <param name='port'>
        /// Port.
        /// </param>
        public int SyncronizeCallback(int clientId, int port)
        {
            return GetConnection(clientId)
				.SynchronizeCallback(port);

        } // SyncronizeCallback
		

        /// <summary>
        /// Shutdown this instance.
        /// </summary>
        public virtual void Shutdown()
        {
            _accessPoint.Disconnect();
			
			// Raise closed event
			OnClosed();

        } // Shutdown


        // --------------------------------------------------------
        // Class Handles
        // --------------------------------------------------------
		
		
		/// <summary>
		/// Raises the connection data received event.
		/// </summary>
		/// <param name='arg1'>
		/// Arg1.
		/// </param>
		/// <param name='arg2'>
		/// Arg2.
		/// </param>
        private void OnConnectionDataReceived(IConnection arg1, DataEventArgs arg2)
        {
			OnDataReceived(new ConnectionDataEventArgs(
				arg1, arg2.Packet));
			
        } // OnConnectionDataReceived
				
		
		/// <summary>
		/// Raises the connection data sent event.
		/// </summary>
		/// <param name='arg1'>
		/// Arg1.
		/// </param>
		/// <param name='arg2'>
		/// Arg2.
		/// </param>
        private void OnConnectionDataSent(IConnection arg1, DataEventArgs arg2)
        {     	
			OnDataSent(new ConnectionDataEventArgs(
				arg1, arg2.Packet));
			
        } // OnConnectionDataSent
		

        /// <summary>
        /// Raises the client joined event.
        /// </summary>
        /// <param name='e'>
        /// E.
        /// </param>
        protected virtual void OnClientJoined(ConnectionEventArgs e)
        {
            if (ClientJoined != null)
                ClientJoined(this, e);

        } // OnClientJoined


        /// <summary>
        /// Raises the client left event.
        /// </summary>
        /// <param name='e'>
        /// E.
        /// </param>
        protected virtual void OnClientLeft(ConnectionEventArgs e)
        {
            if (ClientLeft != null)
                ClientLeft(this, e);

        } // OnClientLeft


        /// <summary>
        /// Raises the data received event.
        /// </summary>
        /// <param name='e'>
        /// E.
        /// </param>
        protected virtual void OnDataReceived(ConnectionDataEventArgs e)
        {
            if (DataReceived != null)
                DataReceived(this, e);

        } // OnDataReceived


        /// <summary>
        /// Raises the data sent event.
        /// </summary>
        /// <param name='e'>
        /// E.
        /// </param>
        protected virtual void OnDataSent(ConnectionDataEventArgs e)
        {
            if (DataSent != null)
                DataSent(this, e);

        } // OnDataSent
		
		
		/// <summary>
		/// Raises the closed event.
		/// </summary>
		protected virtual void OnClosed()
		{
			if(Closed != null)
				Closed(this);
			
		} // OnClosed

    } // class Peer
} // namespace Lypshare.Net.Client